﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Reflection;
using System.IO;
using System.Threading;
using System.Diagnostics;
using Centrify.Cfw;

namespace Player
{
    public static class WindowsApi
    {
        #region Public constants

        // Window messages
        public const int WM_DESTROY = 0x0002;
        public const int WM_SETTEXT = 0x000C;
        public const int WM_CLOSE = 0x0010;
        public const int WM_QUIT = 0x0012;
        public const int WM_SHOWWINDOW = 0x0018;
        public const int LB_GETITEMRECT = 0x0198;
        public const int WM_USER = 0x0400;

        // Bitmap compression methods
        public const uint BI_RGB = 0;
        public const uint BI_RLE8 = 1;
        public const uint BI_RLE4 = 2;
        public const uint BI_BITFIELDS = 3;
        public const uint BI_JPEG = 4;
        public const uint BI_PNG = 5;

        #endregion

        #region Internal constant values

        private const uint DIB_RGB_COLORS = 0;
        private const uint DIB_PAL_COLORS = 1;

        private const uint SRCCOPY = 0x00CC0020;
        private const uint CAPTUREBLT = 0x40000000;

        private const int WH_MOUSE_LL = 14;
        private const int WH_MOUSE = 7;
        private const int WM_MOUSEMOVE = 0x200;
        private const int WM_LBUTTONDOWN = 0x201;
        private const int WM_RBUTTONDOWN = 0x204;
        private const int WM_MBUTTONDOWN = 0x207;
        private const int WM_LBUTTONUP = 0x202;
        private const int WM_RBUTTONUP = 0x205;
        private const int WM_MBUTTONUP = 0x208;
        private const int WM_LBUTTONDBLCLK = 0x203;
        private const int WM_RBUTTONDBLCLK = 0x206;
        private const int WM_MBUTTONDBLCLK = 0x209;
        private const int WM_MOUSEWHEEL = 0x020A;

        private const int CURSOR_SHOWING = 0x00000001;

        private const int DI_MASK = 0x0001;
        private const int DI_IMAGE = 0x0002;
        private const int DI_NORMAL = 0x0003;
        private const int DI_COMPAT = 0x0004;
        private const int DI_DEFAULTSIZE = 0x0008;

        private const uint GENERIC_READ = 0x80000000;
        private const uint GENERIC_WRITE = 0x40000000;
        private const uint GENERIC_EXECUTE = 0x20000000;
        private const uint GENERIC_ALL = 0x10000000;

        private const long DESKTOP_CREATEWINDOW = 0x0002L;
        private const long DESKTOP_ENUMERATE = 0x0040L;
        private const long DESKTOP_WRITEOBJECTS = 0x0080L;
        private const long DESKTOP_SWITCHDESKTOP = 0x0100L;
        private const long DESKTOP_CREATEMENU = 0x0004L;
        private const long DESKTOP_HOOKCONTROL = 0x0008L;
        private const long DESKTOP_READOBJECTS = 0x0001L;
        private const long DESKTOP_JOURNALRECORD = 0x0010L;
        private const long DESKTOP_JOURNALPLAYBACK = 0x0020L;
        private const uint AccessRights = (uint)(DESKTOP_JOURNALRECORD | DESKTOP_JOURNALPLAYBACK | DESKTOP_CREATEWINDOW | DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP | DESKTOP_CREATEMENU | DESKTOP_HOOKCONTROL | DESKTOP_READOBJECTS);

        private const int SPI_GETSCREENSAVERRUNNING = 114;

        private const int UOI_NAME = 2;

        private const int GWL_STYLE = -16;
        private const int GWL_EXSTYLE = -20;

        // Window styles (from WinUser.h)
        private const int WS_OVERLAPPED = 0x00000000;
        //private const int WS_POPUP =             0x80000000;
        private const int WS_CHILD = 0x40000000;
        private const int WS_MINIMIZE = 0x20000000;
        private const int WS_VISIBLE = 0x10000000;
        private const int WS_DISABLED = 0x08000000;
        private const int WS_CLIPSIBLINGS = 0x04000000;
        private const int WS_CLIPCHILDREN = 0x02000000;
        private const int WS_MAXIMIZE = 0x01000000;
        private const int WS_CAPTION = 0x00C00000;     /* WS_BORDER | WS_DLGFRAME  */
        private const int WS_BORDER = 0x00800000;
        private const int WS_DLGFRAME = 0x00400000;
        private const int WS_VSCROLL = 0x00200000;
        private const int WS_HSCROLL = 0x00100000;
        private const int WS_SYSMENU = 0x00080000;
        private const int WS_THICKFRAME = 0x00040000;
        private const int WS_GROUP = 0x00020000;
        private const int WS_TABSTOP = 0x00010000;

        // Extended Windows styles
        private const int WS_EX_DLGMODALFRAME = 0x00000001;
        private const int WS_EX_NOPARENTNOTIFY = 0x00000004;
        private const int WS_EX_TOPMOST = 0x00000008;
        private const int WS_EX_ACCEPTFILES = 0x00000010;
        private const int WS_EX_TRANSPARENT = 0x00000020;
        private const int WS_EX_MDICHILD = 0x00000040;
        private const int WS_EX_TOOLWINDOW = 0x00000080;
        private const int WS_EX_WINDOWEDGE = 0x00000100;
        private const int WS_EX_CLIENTEDGE = 0x00000200;
        private const int WS_EX_CONTEXTHELP = 0x00000400;
        private const int WS_EX_RIGHT = 0x00001000;
        private const int WS_EX_LEFT = 0x00000000;
        private const int WS_EX_RTLREADING = 0x00002000;
        private const int WS_EX_LTRREADING = 0x00000000;
        private const int WS_EX_LEFTSCROLLBAR = 0x00004000;
        private const int WS_EX_RIGHTSCROLLBAR = 0x00000000;
        private const int WS_EX_CONTROLPARENT = 0x00010000;
        private const int WS_EX_STATICEDGE = 0x00020000;
        private const int WS_EX_APPWINDOW = 0x00040000;

        // GetLastError values
        private const int ERROR_ACCESS_DENIED = 5;
        private const int ERROR_NOT_ENOUGH_MEMORY = 8;
        #endregion

        internal static class SafeNativeMethods
        {
            #region Enums and Structures
            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int Left;
                public int Top;
                public int Right;
                public int Bottom;
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct POINT
            {
                public int X;
                public int Y;

                public POINT(int x, int y)
                {
                    this.X = x;
                    this.Y = y;
                }

                public static implicit operator System.Drawing.Point(POINT p)
                {
                    return new System.Drawing.Point(p.X, p.Y);
                }

                public static implicit operator POINT(System.Drawing.Point p)
                {
                    return new POINT(p.X, p.Y);
                }
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct CURSORINFO
            {
                public int cbSize;
                public int flags;
                public IntPtr hCursor;
                public POINT screenPos;
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct ICONINFO
            {
                public bool icon;
                public int xHotspot;
                public int yHotspot;
                public IntPtr hbmMask;
                public IntPtr hbmColor;
            }
            #endregion

            #region User32.dll P/Invokes
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool ClientToScreen(IntPtr hWnd, out POINT lpPoint);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool GetCursorInfo(out CURSORINFO pci);

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            internal static extern int SystemParametersInfo(int uAction, int uParam, ref int lpvParam, int fuWinIni);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool DrawIconEx(IntPtr hdc, int xLeft, int yTop, IntPtr hIcon,
                                                  int cxWidth, int cyHeight, int istepIfAniCur, IntPtr hbrFlickerFree, int diFlags);

            [DllImport("user32.dll", SetLastError = true)]
            internal static extern IntPtr OpenInputDesktop(int dwFlags, [MarshalAs(UnmanagedType.Bool)] bool fInherit, uint dwDesiredAccess);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool GetUserObjectInformation(IntPtr hobj, int nIndex, IntPtr pvInfo, int nLength, ref int lengthNeeded);

            [DllImport("user32.dll")]
            internal static extern IntPtr GetThreadDesktop(uint dwThreadId);

            [DllImport("user32.dll", SetLastError = true)]
            internal static extern IntPtr GetParent(IntPtr hWnd);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool EnumWindows(EnumCallBackPtr callPtr, IntPtr lPar);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool EnumDesktopWindows(IntPtr desktopHandle, EnumCallBackPtr callPtr, IntPtr lParam);

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            internal static extern int GetWindowTextLength(IntPtr hWnd);

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            internal static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool IsWindow(IntPtr hWnd);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool IsWindowVisible(IntPtr hWnd);

            [DllImport("user32.dll", SetLastError = true)]
            internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool IsIconic(IntPtr hWnd);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool CloseDesktop(IntPtr desktopHandle);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool SetThreadDesktop(IntPtr hDesktop);

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

            [DllImport("user32.dll")]
            internal static extern IntPtr GetForegroundWindow();

            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            internal static extern uint GetWindowModuleFileName(IntPtr hwnd,
                                                                [Out] StringBuilder lpszFileName,
                                                                uint cchFileNameMax);

            [DllImport("user32.dll", SetLastError = true)]
            internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

            [DllImport("user32.dll", SetLastError = false)]
            internal static extern IntPtr GetShellWindow();

            [DllImport("user32.dll", SetLastError = false)]
            internal static extern IntPtr GetDesktopWindow();

            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            internal static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

            #endregion

            #region Gdi32.dll P/Invokes

            [DllImport("gdi32.dll", SetLastError = true)]
            internal static extern IntPtr CreateDC(string driver, string device, string output, IntPtr initData);

            [DllImport("gdi32.dll", SetLastError = true)]
            internal static extern IntPtr CreateCompatibleDC(IntPtr hdc);

            [DllImport("gdi32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool DeleteDC(IntPtr hdc);

            [DllImport("gdi32.dll")]
            internal static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

            [DllImport("gdi32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool DeleteObject(IntPtr hObject);

            [DllImport("gdi32.dll", SetLastError = true)]
            internal static extern IntPtr CreateCompatibleBitmap(IntPtr dc, int width, int height);

            [DllImport("gdi32.dll", SetLastError = true)]
            internal static extern IntPtr CreateDIBSection(IntPtr hdc, [In] ref BitmapInfo pbmi, uint iUsage,
                                                            out IntPtr ppvBits, IntPtr hSection, uint dwOffset);

            [DllImport("gdi32.dll", SetLastError = true)]
            internal static extern int GetDIBits(IntPtr hdc, IntPtr hbmp, uint uStartScan,
                                                uint cScanLines, [Out] byte[] lpvBits, ref BitmapInfo lpbmi, uint uUsage);

            [DllImport("gdi32.dll", SetLastError = true)]
            internal static extern int SetDIBColorTable(IntPtr hdc, uint uStartIndex, uint entries, ref RGBQUAD[] colors);

            [DllImport("gdi32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth,
                                              int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);

            #endregion

            #region Kernel32.dll P/Invokes

            [DllImport("kernel32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool FreeConsole();

            [DllImport("kernel32.dll")]
            internal static extern void SetLastError(IntPtr errorCode);

            [DllImport("kernel32.dll")]
            internal static extern uint GetCurrentThreadId();

            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool GetDiskFreeSpaceEx(string lpDirectoryName,
                out ulong lpFreeBytesAvailable,
                out ulong lpTotalNumberOfBytes,
                out ulong lpTotalNumberOfFreeBytes);

            #endregion

            #region DbgHelp.dll P/Invokes
            [DllImport("dbghelp.dll")]
            internal static extern bool MiniDumpWriteDump(IntPtr hProcess, Int32 ProcessId, IntPtr hFile,
                MINIDUMP_TYPE DumpType, IntPtr ExceptionParam, IntPtr UserStreamParam, IntPtr CallackParam);
            #endregion
        }

        #region Public Enumerations and structures

        public enum MINIDUMP_TYPE
        {
            MiniDumpNormal = 0x00000000,
            MiniDumpWithDataSegs = 0x00000001,
            MiniDumpWithFullMemory = 0x00000002,
            MiniDumpWithHandleData = 0x00000004,
            MiniDumpFilterMemory = 0x00000008,
            MiniDumpScanMemory = 0x00000010,
            MiniDumpWithUnloadedModules = 0x00000020,
            MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
            MiniDumpFilterModulePaths = 0x00000080,
            MiniDumpWithProcessThreadData = 0x00000100,
            MiniDumpWithPrivateReadWriteMemory = 0x00000200,
            MiniDumpWithoutOptionalData = 0x00000400,
            MiniDumpWithFullMemoryInfo = 0x00000800,
            MiniDumpWithThreadInfo = 0x00001000,
            MiniDumpWithCodeSegs = 0x00002000,
            MiniDumpWithoutAuxiliaryState = 0x00004000,
            MiniDumpWithFullAuxiliaryState = 0x00008000,
            MiniDumpWithPrivateWriteCopyMemory = 0x00010000,
            MiniDumpIgnoreInaccessibleMemory = 0x00020000,
            MiniDumpWithTokenInformation = 0x00040000
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct BITMAPINFOHEADER
        {
            public uint biSize;
            public int biWidth;
            public int biHeight;
            public ushort biPlanes;
            public ushort biBitCount;
            public uint biCompression;
            public uint biSizeImage;
            public int biXPelsPerMeter;
            public int biYPelsPerMeter;
            public uint biClrUsed;
            public uint biClrImportant;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct RGBQUAD
        {
            public byte rgbBlue;
            public byte rgbGreen;
            public byte rgbRed;
            public byte rgbReserved;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct BitmapInfo
        {
            public BITMAPINFOHEADER bmiHeader;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
            public RGBQUAD[] bmiColors;
        }

        public class DiskInfoStruct
        {
            public ulong FreeBytesAvailable;
            public ulong TotalNumberOfBytes;
            public ulong TotalNumberOfFreeBytes;
        }

        #endregion

        #region Wrapper methods and delegates

        public static DiskInfoStruct GetDiskFreeSpace(string dirName)
        {
            DiskInfoStruct info = new DiskInfoStruct();
            if (SafeNativeMethods.GetDiskFreeSpaceEx(dirName,
                out info.FreeBytesAvailable,
                out info.TotalNumberOfBytes,
                out info.TotalNumberOfFreeBytes))
            {
                return info;
            }

            Log.Error("Win32 error getting disk free space of {0}: {1}",
                                dirName,
                                new Win32Exception(Marshal.GetLastWin32Error()).Message);

            return null;
        }

        public static IntPtr FindWindow(string className, string windowName)
        {
            return SafeNativeMethods.FindWindow(className, windowName);
        }

        public static IntPtr FindWindowEx(IntPtr parentWindow, IntPtr childWindow, string className, string windowName)
        {
            return SafeNativeMethods.FindWindowEx(parentWindow, childWindow, className, windowName);
        }

        // Helper for FindDesktopBackgroundWindow()
        private static IntPtr FindDesktopBackgroundFromHwnd(IntPtr hwnd)
        {
            if (hwnd != null && hwnd != IntPtr.Zero)
            {
                IntPtr shell = FindWindowEx(hwnd, IntPtr.Zero, "SHELLDLL_DefView", null);
                if (shell != null && shell != IntPtr.Zero)
                {
                    return FindWindowEx(shell, IntPtr.Zero, "SysListView32", "FolderView");
                }
            }
            return IntPtr.Zero;
        }

        public static IntPtr FindDesktopBackgroundWindow()
        {
            try
            {
                // First see if we can find it using the shell window - as then we don't have to search                
                IntPtr result = FindDesktopBackgroundFromHwnd(GetShellWindow());

                if (result == null || result == IntPtr.Zero)
                {
                    // If we haven't found it, then shell window didn't work, let's search from the top
                    result = FindDesktopBackgroundFromHwnd(FindWindow("Progman", "Program Manager"));
                }

                return result;
            }
            catch (Exception e)
            {
                Log.Warning("Unable to locate desktop background window: " + e);
                return IntPtr.Zero;
            }
        }

        public static IntPtr FindDesktopShellWindow()
        {
            try
            {
                IntPtr shell = GetShellWindow();
                if (shell != null && shell != IntPtr.Zero)
                {
                    return FindWindowEx(shell, IntPtr.Zero, "SHELLDLL_DefView", null);
                }

                return IntPtr.Zero;
            }
            catch (Exception e)
            {
                Log.Warning("Unable to locate desktop shell window: " + e);
                return IntPtr.Zero;
            }
        }

        public static IntPtr GetDesktopWindow()
        {
            return SafeNativeMethods.GetDesktopWindow();
        }

        public static IntPtr GetShellWindow()
        {
            return SafeNativeMethods.GetShellWindow();
        }

        public static bool MiniDumpToFile(string file, MINIDUMP_TYPE type)
        {
            // Open+Append or Create
            FileStream fileStream = File.Create(file);
            Process me = Process.GetCurrentProcess();
            bool success = WindowsApi.SafeNativeMethods.MiniDumpWriteDump(me.Handle, me.Id,
                fileStream.SafeFileHandle.DangerousGetHandle(), type,
                IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
            fileStream.Flush();
            fileStream.Close();
            return success;
        }

        private static void MiniDumpToFileInNewThreadWorker(object arg)
        {
            try
            {
                string file = (string)arg;
                MiniDumpToFile(file,
                    MINIDUMP_TYPE.MiniDumpNormal |
                    MINIDUMP_TYPE.MiniDumpWithThreadInfo |
                    MINIDUMP_TYPE.MiniDumpWithFullMemoryInfo |
                    MINIDUMP_TYPE.MiniDumpWithProcessThreadData |
                    MINIDUMP_TYPE.MiniDumpWithHandleData |
                    MINIDUMP_TYPE.MiniDumpWithFullMemory |
                    MINIDUMP_TYPE.MiniDumpWithDataSegs);
            }
            catch (Exception e)
            {
                // We cannot throw, lest we invoke an unhandled exception in this thread... and hit a new dump request..
                // which would lead us here again.. and again.. and again.
                //Event.Error(CentrifyException.ExtendedMessageWithStackTrace("Exception while dumping minidump to file",
                                                                            //e));
            }
        }

        public static void MiniDumpToFileInNewThread(string file)
        {
            Thread waitThread = new Thread(MiniDumpToFileInNewThreadWorker);
            waitThread.Start(file);
            waitThread.Join();
        }

        public static uint GetWindowProcessId(IntPtr hwnd)
        {
            uint pid = 0;
            WindowsApi.SafeNativeMethods.GetWindowThreadProcessId(hwnd, out pid);
            return pid;
        }

        public static uint GetWindowThreadId(IntPtr hwnd)
        {
            uint tid = 0;
            uint pid = 0;
            return WindowsApi.SafeNativeMethods.GetWindowThreadProcessId(hwnd, out pid);
        }

        // Delegate for EnumerateWindows() call
        public delegate bool EnumCallBackPtr(IntPtr hwnd, IntPtr lParam);

        public static bool DeleteDC(IntPtr dc)
        {
            return SafeNativeMethods.DeleteDC(dc);
        }

        // Detach and close console window
        public static bool FreeConsole()
        {
            return SafeNativeMethods.FreeConsole();
        }

        public static uint CurrentThreadId()
        {
            return SafeNativeMethods.GetCurrentThreadId();
        }

        public static IntPtr GetForegroundWindow()
        {
            return SafeNativeMethods.GetForegroundWindow();
        }

        public static bool EnumerateWindows(EnumCallBackPtr callPtr, IntPtr lPar)
        {
            return SafeNativeMethods.EnumWindows(callPtr, lPar);
        }

        public static string GetClassName(IntPtr hwnd)
        {
            StringBuilder className = new StringBuilder(128);
            if (SafeNativeMethods.GetClassName(hwnd, className, 128) != 0)
                return className.ToString();
            else
                return null;
        }

        public static IntPtr OpenInputDesktop()
        {
            return SafeNativeMethods.OpenInputDesktop(0, false, AccessRights);
        }

        public static void CloseDesktop(IntPtr desktop)
        {
            SafeNativeMethods.CloseDesktop(desktop);
        }

        public static IntPtr CurrentDesktop()
        {
            return SafeNativeMethods.GetThreadDesktop(CurrentThreadId());
        }

        public static string CurrentDesktopName()
        {
            IntPtr cDesktop = CurrentDesktop();
            if (cDesktop == IntPtr.Zero)
                return null;
            string name = DesktopName(cDesktop);
            SafeNativeMethods.CloseDesktop(cDesktop);
            return name;
        }

        public static string InputDesktopName()
        {
            IntPtr cDesktop = OpenInputDesktop();
            if (cDesktop == IntPtr.Zero)
                return null;
            string name = DesktopName(cDesktop);
            SafeNativeMethods.CloseDesktop(cDesktop);
            return name;
        }

        public static string DesktopName(IntPtr desktopHandle)
        {
            if (desktopHandle == IntPtr.Zero) return null;

            // find out how much space to alloc
            int need = 0;
            SafeNativeMethods.GetUserObjectInformation(desktopHandle, UOI_NAME, IntPtr.Zero, 0, ref need);

            // Allocate, then get
            string name = null;
            IntPtr ptr = Marshal.AllocHGlobal(need);
            if (SafeNativeMethods.GetUserObjectInformation(desktopHandle, UOI_NAME, ptr, need, ref need))
            {
                name = Marshal.PtrToStringAnsi(ptr);
            }
            Marshal.FreeHGlobal(ptr);
            return name;
        }

        //         // Is the current desktop the input desktop?
        //         public static bool IsInputDesktop()
        //         {
        //             bool ret = false;

        //             IntPtr cDesktop = CurrentDesktop();
        //             IntPtr iDesktop = OpenInputDesktop();

        //             if(DesktopName(cDesktop) == DesktopName(iDesktop))
        //                 ret = true;

        //             SafeNativeMethods.CloseDesktop(iDesktop);
        //             SafeNativeMethods.CloseDesktop(cDesktop);
        //             return ret;
        //         }

        // Switch to the input desktop
        public static bool SwitchToInputDesktop()
        {
            IntPtr iDesktop = OpenInputDesktop();
            if (iDesktop == IntPtr.Zero)
                return false;

            IntPtr cDesktop = CurrentDesktop();
            if (cDesktop == IntPtr.Zero)
            {
                SafeNativeMethods.CloseDesktop(iDesktop);
                return false;
            }

            bool rc = SafeNativeMethods.SetThreadDesktop(iDesktop);
            SafeNativeMethods.CloseDesktop(iDesktop);
            return rc;
        }

        public static Rectangle GetCursorRectangle()
        {
            int x = Cursor.Position.X - Cursor.Current.HotSpot.X;
            int y = Cursor.Position.Y - Cursor.Current.HotSpot.Y;
            int w = Cursor.Current.Size.Width;
            int h = Cursor.Current.Size.Height;

            Rectangle foo = new Rectangle(x, y, w, h);
            foo.Intersect(SystemInformation.VirtualScreen);  // Don't exceed the screen's rectangle
            return foo;
        }

        // Get a single scanline and call GetDIBits twice to fill out an sBitmapInfo class
        public static BitmapInfo GetBitmapInfo()
        {
            IntPtr hdc = CreateDisplayDC();
            if (hdc == IntPtr.Zero)
                throw new ApplicationException("Unable to get bitmap info, creation of display dc failed.");

            BitmapInfo info = GetBitmapInfo(hdc);
            SafeNativeMethods.DeleteDC(hdc);

            // We have our results, return them         
            return info;
        }

        // Get bitmap info for the given hdc
        public static BitmapInfo GetBitmapInfo(IntPtr hdc)
        {
            IntPtr bmp = SafeNativeMethods.CreateCompatibleBitmap(hdc, 1, 1);
            if (bmp == IntPtr.Zero)
                throw new ApplicationException("Unable to create compatible bitmap: " + Marshal.GetLastWin32Error());

            BitmapInfo binfo = new BitmapInfo();
            binfo.bmiHeader = new BITMAPINFOHEADER();
            binfo.bmiColors = new RGBQUAD[256];
            binfo.bmiHeader.biBitCount = 0;
            binfo.bmiHeader.biSize = 40;

            // Call GetDIBits to get bitmap information
            if (SafeNativeMethods.GetDIBits(hdc, bmp, 0, 1, null, ref binfo, DIB_RGB_COLORS) == 0)
            {
                int lastError = Marshal.GetLastWin32Error();
                string message = "Unable to GetDIBits first time: " + lastError;
                Log.Verbose("{0}", message);
                SafeNativeMethods.DeleteDC(hdc);
                SafeNativeMethods.DeleteObject(bmp);
                throw new ApplicationException(message);
            }

            // Yes, we call this twice.  The second time around it will fill in 
            //   the color member of binfo with a 3 DWORD mask for color masking.  
            //   This isn't very clear from the docs, but it is mentioned in the 
            //   info on BITMAPINFOHEADER, when discussing the meaning of the
            //   BI_BITFIELD value for biCompression member.  No idea why you have to
            //   call this twice to get this information.. but it seems to work.
            if (SafeNativeMethods.GetDIBits(hdc, bmp, 0, 1, null, ref binfo, DIB_RGB_COLORS) == 0)
            {
                int lastError = Marshal.GetLastWin32Error();
                string message = "Unable to GetDIBits second time: " + lastError;
                Log.Verbose("{0}", message);
                SafeNativeMethods.DeleteDC(hdc);
                SafeNativeMethods.DeleteObject(bmp);
                throw new ApplicationException(message);
            }

            SafeNativeMethods.DeleteObject(bmp);
            return binfo;
        }

        // Static bool set to true when our environment is XP - we use this to avoid having to
        //  check the System.Environment properties everytime 
        public static bool isWindowsXP = (System.Environment.OSVersion.Platform == PlatformID.Win32NT &&
                                            System.Environment.OSVersion.Version.Major == 5 &&
                                            System.Environment.OSVersion.Version.Minor == 1);

        public class ScreenAccessDeniedException : Exception { }

        // An always available exception handle, when we throw these, we are likely to throw them a lot 
        //  as we are generally in a hard loop (collecting portions of screen), so let's not eat up lots 
        //  of memory by newing one everytime.
        private static ScreenAccessDeniedException m_accessDeniedException = new ScreenAccessDeniedException();


        public static byte[] GetScreenBits(Rectangle rect, bool includeCursor, bool useCaptureBlt)
        {
#if DEBUG
            bool SimulateCreateDisplayFail = RegistryReader.GetMachineInt(@"CFW\WindowsAPI", "SimulateCreateDisplayFail", false, 0) != 0x00;
            bool SimulateCreateDIBFail = RegistryReader.GetMachineInt(@"CFW\WindowsAPI", "SimulateCreateDIBFail", false, 0) != 0x00;
            bool SimulateBitBlitOOM = RegistryReader.GetMachineInt(@"CFW\WindowsAPI", "SimulateBitBlitOOM", false, 0) != 0x00;
            bool SimulateBitBlitAccessDenied = RegistryReader.GetMachineInt(@"CFW\WindowsAPI", "SimulateBitBlitAccessDenied", false, 0) != 0x00;
            bool SimulateGetDIBFail = RegistryReader.GetMachineInt(@"CFW\WindowsAPI", "SimulateGetDIBFail", false, 0) != 0x00;
#else
            bool SimulateCreateDisplayFail = false;
            bool SimulateCreateDIBFail = false;
            bool SimulateBitBlitOOM = false;
            bool SimulateBitBlitAccessDenied = false;
            bool SimulateGetDIBFail = false;
#endif
            IntPtr displayDc = CreateDisplayDC();
            IntPtr compatDc = SafeNativeMethods.CreateCompatibleDC(displayDc);

            if (compatDc == IntPtr.Zero || SimulateCreateDisplayFail)
            {
                int error = SimulateCreateDisplayFail ? 0 : Marshal.GetLastWin32Error();
                string message = "Unable to create a compatible display DC: " + error;
                if (SimulateCreateDisplayFail) SafeNativeMethods.DeleteDC(compatDc);

                Log.Verbose("{0}", message);
                SafeNativeMethods.DeleteDC(displayDc);
                throw new ApplicationException(message);
            }

            /***
             * We use a DIB section for our capture.  MSDN samples use a Compatible bitmap, like:
             * 
             * IntPtr hbmp = CreateCompatibleBitmap(displayDc, rect.Width, rect.Height);
             *  (See: http://msdn2.microsofwhot.com/en-us/library/ms532314.aspx)
             * 
             * Then we'd use GetDIBits() to get the format and eventually the bits themselves out of
             * hbmp.  HOWEVER, in RDP sessions this exposes a bug in the rdp client/driver combo of
             * some sort.  Essentially, when doing a CreateCompatibleBitmap of a size which is a 
             * large % of the total screen size, some paints get dropped at the RDP level and are 
             * not seen by the client.  This is in our bugzilla as bug #10659.    
             * 
             * At the suggestion of Greg Binkerd, Microsoft's GDI guy, we now use a DIB section, which should
             * also be a bit faster, but requires that we know the screens pixel format *first*.  So we reverse
             * our order of operations and use GetDIBits to get the screens pixel format, THEN create a DIB section
             * and BitBlt into it.
             * 
             * Note that we  opened a case with MS, as part of SRX071203601229.  Microsoft considers the workaround
             * provided sufficient and does not plan to address the RDP issue.
             */
            IntPtr hbmp = IntPtr.Zero;
            IntPtr screenBits = IntPtr.Zero;
            BitmapInfo screenInfo = GetBitmapInfo(displayDc);
            screenInfo.bmiHeader.biHeight = -rect.Height;
            screenInfo.bmiHeader.biWidth = rect.Width;

            int bufferWidth = rect.Width * (screenInfo.bmiHeader.biBitCount / 8);
            // dword aligned!
            int rem = bufferWidth % 4;
            if (rem != 0) bufferWidth += 4 - rem;
            long bufferSize = rect.Height * bufferWidth;

            /*** 
             * So, now that you grok the reason for using CreateDIBSection, here's a curve ball:
             * The exception to this is when we need to draw the cursor as well.  An 8 bit DIB section + DrawIconEx
             * don't play well together either.  So in the case where we're 8 bit, and we are to include the 
             * cursor, we fall back on CreateCompatibleBitmap(). Because this is always a small rect (32x32 usually)
             * we won't cause the refresh bug to show up and can safely go this (easier) route.
             */
            if (screenInfo.bmiHeader.biBitCount == 8 && includeCursor)
            {
                hbmp = SafeNativeMethods.CreateCompatibleBitmap(displayDc, rect.Width, rect.Height);
            }
            else
            {
                hbmp = SafeNativeMethods.CreateDIBSection(displayDc, ref screenInfo, DIB_RGB_COLORS,
                                                out screenBits, IntPtr.Zero, 0);
                if (hbmp == IntPtr.Zero || SimulateCreateDIBFail)
                {
                    int error = SimulateCreateDIBFail ? 0 : Marshal.GetLastWin32Error();
                    string message = "Unable to create a dib section: " + error;
                    if (SimulateCreateDIBFail) SafeNativeMethods.DeleteObject(hbmp);
                    Log.Verbose("{0}", message);

                    // Exception                    
                    SafeNativeMethods.DeleteDC(compatDc);
                    SafeNativeMethods.DeleteDC(displayDc);
                    throw new ApplicationException(message);
                }
            }

            // Lots of unmanaged bytes allocated, lets tell the GC
            GC.AddMemoryPressure(bufferSize);

            // First select our bitmap into the compatible dc
            IntPtr oldSelect = SafeNativeMethods.SelectObject(compatDc, hbmp);

            // Avoid the use of CAPTUREBLT on Windows XP when remoted in via RDP.  This flag allows us
            //  to capture transparent windows and self-drawn windows which overlay the desktop.  However,
            //  when in RDP mode on XP, this also causes the mouse cursor to jump (bug in bitblt).  So we
            //  only use this flag on non-XP, or on XP in local mode.  
            //      Case in point: AlphaClock from http://www.irnis.net/files/aclock.exe
            //
            // This is a Windows XP Bug - Confirmed by Microsoft as a result of developer incident: SRX071203601229
            //   they have decided that they will not address this issue.  They are looking at updating the documentation
            //   for the BitBlt call at least, but there is no garauntee that will occur either.  See our bugzilla #10383.
            //   To quote:
            //
            //  "You can send this to your support contacts ... and others who ask about the issue ...:
            //    
            //    The jagged mouse cursor movement observed when calling BitBlt with CAPTUREBLT when remoting into the system
            // via Remote Desktop is a bug with Windows XP.  Microsoft is aware of this problem."
            //
            // Yay <sigh>.            

            uint blitFlags = SRCCOPY;
            if (useCaptureBlt) blitFlags |= CAPTUREBLT;

            if (!SafeNativeMethods.BitBlt(compatDc, 0, 0, rect.Width, rect.Height, displayDc, rect.X, rect.Y, blitFlags) || SimulateBitBlitAccessDenied || SimulateBitBlitOOM)
            {
                int lastError = (SimulateBitBlitAccessDenied || SimulateBitBlitOOM) ? 0 : Marshal.GetLastWin32Error();
                if (SimulateBitBlitAccessDenied) lastError = ERROR_ACCESS_DENIED;

                SafeNativeMethods.DeleteObject(hbmp);
                SafeNativeMethods.DeleteDC(compatDc);
                SafeNativeMethods.DeleteDC(displayDc);
                GC.RemoveMemoryPressure(bufferSize);

                if (lastError != ERROR_ACCESS_DENIED || SimulateBitBlitOOM)
                {
                    string message = "Unable to BitBlt screen bits into compatible dc: " + lastError;
                    Log.Verbose("{0}", message);

                    if (lastError == ERROR_NOT_ENOUGH_MEMORY)
                        throw new OutOfMemoryException(message);
                    else
                        throw new ApplicationException(message);
                }
                else
                {
                    // So it turns out this is an acceptable condition, when our primary screen is 
                    //  in a terminal services session, and the TS client is minimized or hiding, then
                    //  the rdp driver is not drawing anything and we get ERROR_ACCESS_DENIED.  Throw 
                    //  it up with a custom exception so those calling us can handle accordingly.
                    throw m_accessDeniedException;
                }
            }

            // If includeCursor is set, draw the current cursor as well (at 0, 0)
            if (includeCursor)
            {
                // We have to use the GetCursorInfo call, as the System.Windows.Forms.Cursor only provides 
                //  a handle for OUR cursor, not for the global one.                
                SafeNativeMethods.CURSORINFO cinfo = new SafeNativeMethods.CURSORINFO();
                cinfo.cbSize = Marshal.SizeOf(typeof(SafeNativeMethods.CURSORINFO));
                if (SafeNativeMethods.GetCursorInfo(out cinfo))
                {
                    if (cinfo.hCursor != IntPtr.Zero && cinfo.flags == CURSOR_SHOWING)
                        SafeNativeMethods.DrawIconEx(compatDc, 0, 0, cinfo.hCursor, 0, 0, 0, IntPtr.Zero, DI_NORMAL | DI_COMPAT);
                }
            }

            // Must restore old selection 
            SafeNativeMethods.SelectObject(compatDc, oldSelect);


            // Make a buffer to get the bits into            
            byte[] bits = new byte[rect.Height * bufferWidth];

            // How we get the bits out depends on whether we used a DIB section or CCB
            if (screenInfo.bmiHeader.biBitCount == 8 && includeCursor)
            {
                // Use GetDIBits()
                if (SafeNativeMethods.GetDIBits(compatDc, hbmp, 0, (uint)rect.Height, bits, ref screenInfo, DIB_RGB_COLORS) == 0 || SimulateGetDIBFail)
                {
                    int lastError = SimulateGetDIBFail ? 0 : Marshal.GetLastWin32Error();
                    string message = "Unable to GetDIBits from screen: " + lastError;
                    Log.Verbose("{0}", message);
                    SafeNativeMethods.DeleteObject(hbmp);
                    SafeNativeMethods.DeleteDC(compatDc);
                    SafeNativeMethods.DeleteDC(displayDc);
                    GC.RemoveMemoryPressure(bufferSize);
                    throw new ApplicationException(message);
                }
            }
            else
            {
                // Copy out the bits from the DIB section
                Marshal.Copy(screenBits, bits, 0, bits.Length);
            }

            SafeNativeMethods.DeleteObject(hbmp);
            SafeNativeMethods.DeleteDC(compatDc);
            SafeNativeMethods.DeleteDC(displayDc);
            GC.RemoveMemoryPressure(bufferSize);

            return bits;
        }

        public static IntPtr CreateDisplayDC()
        {
            IntPtr displayDc = SafeNativeMethods.CreateDC("DISPLAY", null, null, IntPtr.Zero);
            if (displayDc == IntPtr.Zero)
            {
                string message = "Unable to create a display DC: " + Marshal.GetLastWin32Error();
                Log.Verbose("{0}", message);
            }

            return displayDc;
        }

        public static Point ClientToScreen(int hwnd)
        {
            SafeNativeMethods.POINT p = new SafeNativeMethods.POINT();
            SafeNativeMethods.ClientToScreen((IntPtr)hwnd, out p);
            return (Point)p;
        }

        public static bool IsIconic(int hwnd)
        {
            return SafeNativeMethods.IsIconic((IntPtr)hwnd);
        }

        public static bool IsWindow(int hwnd)
        {
            return SafeNativeMethods.IsWindow((IntPtr)hwnd);
        }

        public static bool IsWindowVisible(int hwnd)
        {
            return SafeNativeMethods.IsWindowVisible((IntPtr)hwnd);
        }

        public static int GetParent(int hwnd)
        {
            SafeNativeMethods.SetLastError((IntPtr)0);
            int parentWnd = (int)SafeNativeMethods.GetParent((IntPtr)hwnd);
            if (parentWnd == 0)
            {
                // This may or may not be an error.  Get error code to find out.
                int lastError = Marshal.GetLastWin32Error();
                if (lastError != 0)
                {
                    Log.Verbose("WINAPI ERROR getting parent of {0}: {1}",
                                hwnd,
                                new Win32Exception(lastError).Message);
                }
            }
            return parentWnd;
        }

        // If the enumerated window matches our search window, then its top
        //  level, stop iterating.
        private static bool EnumTopLevel(IntPtr hwnd, IntPtr lparam)
        {
            if (hwnd == lparam)
                return false;   // stop enumerating, cause EnumerateWindows to fail
            else
                return true;    // next
        }

        public static bool IsTopLevelWindow(int hwnd)
        {
            // If its owned, its not top level
            //             int parentWnd = GetParent(hwnd);
            //             if (parentWnd != 0)
            //             {
            //                 Log.Verbose("{0} is not a top level window because it has a parent of {1}", hwnd, parentWnd);
            //                 return false;
            //             }

            // TBD: this check doesn't work.  Empirically when top level windows are first created they
            // may not be visible.

            //             // If its not visible, we dont care
            //             if (!SafeNativeMethods.IsWindowVisible((IntPtr)hwnd))
            //             {
            //                 Log.Verbose("{0} is not a top level window because it isn't visible", hwnd);
            //                 return false;
            //             }

            // TBD: disable this check for now.  Our simpleclock is a "tool" window.

            //             // if its a tool window its not top level
            //             if ((GetWindowExStyle(hwnd) & WS_EX_TOOLWINDOW) != 0)
            //             {
            //                 Log.Verbose("{0} is not a top level window because it is a tool window", hwnd);
            //                 return false;
            //             }

            // TBD: disabled for now, WPF apps don't have the caption style
            // If it doesn't have a caption style, it's not a top level window.  This does permit
            // dialog boxes through, but for now I think we want to capture these as well.
            //            if ((GetWindowStyle(hwnd) & WS_CAPTION) != WS_CAPTION)
            //            {
            //                Log.Verbose("{0} is not a 'top' level window because it doesn't have a caption", hwnd);
            //                return false;
            //            }

            // EnumerateWindows will *fail* if hwnd is found, so if it succeeds, hwnd is NOT found, and is therefor not top level
            if (EnumerateWindows(new EnumCallBackPtr(EnumTopLevel), (IntPtr)hwnd))
                return false;

            return true;
        }

        // Returns an empty rectangle on failure
        public static Rectangle GetWindowRectangle(int hwnd)
        {
            SafeNativeMethods.RECT r = new SafeNativeMethods.RECT();
            r.Top = r.Left = r.Right = r.Bottom = 0;
            SafeNativeMethods.GetWindowRect((IntPtr)hwnd, out r);
            return new Rectangle(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
        }

        // Returns an empty rectangle on failure
        public static Rectangle GetClientRectangle(int hwnd)
        {
            SafeNativeMethods.RECT r = new SafeNativeMethods.RECT();
            r.Top = r.Left = r.Right = r.Bottom = 0;
            SafeNativeMethods.GetClientRect((IntPtr)hwnd, out r);
            return new Rectangle(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
        }

        public static string GetWindowTitle(int hwnd)
        {
            // Allocate correct string length first
            int length = SafeNativeMethods.GetWindowTextLength((IntPtr)hwnd);
            StringBuilder sb = new StringBuilder(length + 1);
            SafeNativeMethods.GetWindowText((IntPtr)hwnd, sb, sb.Capacity);
            return sb.ToString();
        }

        public static int GetWindowStyle(int hwnd)
        {
            SafeNativeMethods.SetLastError((IntPtr)0);
            int style = SafeNativeMethods.GetWindowLong((IntPtr)hwnd, GWL_STYLE);
            if (style == 0)
            {
                // This may or may not be an error.  Get error code to find out.
                int lastError = Marshal.GetLastWin32Error();
                if (lastError != 0)
                {
                    Log.Verbose("WINAPI ERROR getting style of {0}: {1}",
                                hwnd,
                                new Win32Exception(lastError).Message);
                }
            }
            return style;
        }

        public static string GetModuleName(IntPtr hwnd)
        {
            StringBuilder moduleNameBuilder = new StringBuilder(2048);
            uint rc = SafeNativeMethods.GetWindowModuleFileName(hwnd, moduleNameBuilder, 2048);
            if (rc == 0)
                return "unknown";
            string moduleName = moduleNameBuilder.ToString();
            int lastSlash = moduleName.LastIndexOf('\\');
            if (lastSlash >= 0)
                moduleName = moduleName.Substring(lastSlash);
            return moduleName;
        }

        public static int GetWindowExStyle(IntPtr hwnd)
        {
            SafeNativeMethods.SetLastError((IntPtr)0);
            int exStyle = SafeNativeMethods.GetWindowLong(hwnd, GWL_EXSTYLE);
            if (exStyle == 0)
            {
                // This may or may not be an error.  Get error code to find out.
                int lastError = Marshal.GetLastWin32Error();
                if (lastError != 0)
                {
                    Log.Verbose("WINAPI ERROR getting extended style of {0}: {1}",
                                hwnd,
                                new Win32Exception(lastError).Message);
                }
            }
            return exStyle;
        }

        public static bool IsWindowTopmostStyle(IntPtr hwnd)
        {
            return ((GetWindowExStyle(hwnd) & WS_EX_TOPMOST) != 0x00);
        }

        public static bool AmI64Bit()
        {
            return (Marshal.SizeOf(typeof(IntPtr)) == 8);
        }

        public static bool IsScreenSaverRunning()
        {
            int isRunning = -1;
            if (SafeNativeMethods.SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, ref isRunning, 0) != 0)
            {
                if (isRunning != 0)
                    return true;
            }

            return false;
        }

        #endregion
    }
}